home *** CD-ROM | disk | FTP | other *** search
/ Chip 2003 November / CHIP_CD_2003-11.iso / software / phoa / phoa-setup.exe / {app} / API / phPhoa.pas < prev   
Encoding:
Pascal/Delphi Source File  |  2003-08-30  |  43.4 KB  |  968 lines

  1. //*****************************************************************************
  2. //
  3. // PhoA file format description
  4. // The whole code (c)2003 Dmitry Kann, except where otherwise explicitly noted
  5. // Home sites:
  6. //   http://devtools.narod.ru/
  7. //   http://phoa.narod.ru/
  8. //
  9. // ATTENTION! None of this code can be reproduced in any form without prior
  10. // permission issued by the author.
  11. //
  12. // This unit describes general photo album file structure introduced in PhoA
  13. // picture arranging program in release 1.1.1a. This revision is known as
  14. // 'Revision 3' of .phoa file, and is the first one that supports so-called
  15. // chunk-based organization, and therefore is extensible. Revisions 1 and 2
  16. // were fixed-structure binary files, now they are proprietary PhoA formats
  17. // supported only by the program itself. Their specifications may be supplied
  18. // on an additional request.
  19. //
  20. // Contact email: phoa@narod.ru
  21. //
  22. // Target platform: Borland Delphi 7
  23. // Target OS:       Windows
  24. // Language:        Object Pascal
  25. //
  26. //*****************************************************************************
  27. unit phPhoa;
  28.  
  29. interface
  30. uses SysUtils, Windows, Classes;
  31.  
  32.    // 1. Introduction
  33.    // ===============
  34.    // Photo album files (phoa-files) are binary files accessible only with
  35.    // special programs. Native creator of .phoa is PhoA - picture arranging
  36.    // tool, which is available for free download at http://phoa.narod.ru/en
  37.    //
  38.    // Phoa-files contain different data on how pictures are arranged in the
  39.    // album. At the moment there are five major logical sections in the
  40.    // phoa-file data:
  41.    //
  42.    //  - File header
  43.    //  - General photo album data
  44.    //  - Pictures and their data as they appear in the phoa storage
  45.    //  - Groups hierarchy and links to the pictures
  46.    //  - Additional album data and structures
  47.    //
  48.    // 2. Photo album file sections
  49.    // ============================
  50.    // 2.1 File header
  51.    // ---------------
  52.    // Phoa-file always starts with ASCII string 'PhoA [PhotoAlbum] project
  53.    // file', represented by SPhoAFileSignature constant.
  54.    //
  55.    // Next to the signature follows Revision Number: 4-byte signed integer,
  56.    // Intel byte-order. Current Revision Number is the value of the
  57.    // IPhFileRevisionNumber constant.
  58.    //
  59.    // The header contents are constant and do not differ between revisions
  60.    // (except Revision Number itself). This allows reliable recognition
  61.    // of newer revisions, with subsequent denial of opening, and also
  62.    // allows to build an unified opening procedure (at least for header).
  63.    //
  64.    // WARNING:
  65.    // All information below refers to Revision 3 (see the header of this file
  66.    // for details). Next sections operate with chunks as data storage blocks.
  67.    //
  68.    // 2.2 General photo album data
  69.    // ----------------------------
  70.    // These data include common photo album data such as album description or
  71.    // thumbnail compression rate, as described by corresponding chunks.
  72.    //
  73.    // 2.3 Pictures and their data as they appear in the phoa storage
  74.    // --------------------------------------------------------------
  75.    // A planar list of pictures, enclosed with the list open/close-chunks
  76.    //
  77.    // 2.4 Groups hierarchy and links to the pictures
  78.    // ----------------------------------------------
  79.    // Hierarchically (recursively) organized group tree, each group maintaining
  80.    // list of child groups as well as list of picture IDs 
  81.    //
  82.    // 2.5 Additional album data and structures
  83.    // ----------------------------------------
  84.    // Other photo album data, such as views
  85.    //
  86.    // These sections are pretty relative (except 2.1), and must be overseen in
  87.    // each case.
  88.    //
  89.    // 3. Chunks
  90.    // =========
  91.    // Starting with the section 2.2 (right after header), all phoa-file data
  92.    // are organized with chunks (Revision 3+ !)  
  93.    // A CHUNK is an unsigned 2-byte Intel-ordered value, ranged from $0000 to
  94.    // $ffff, each value has its own predetermined meaning (if any), described
  95.    // in IPhChunk_xxxxxx constants. Each chunk has its own, fixed datatype,
  96.    // which can never be changed in later releases. So if you have read chunk's
  97.    // code, you can say for sure what datatype it contains. Moreover, each
  98.    // chunk is followed by one-byte datatype code, and then datatype-specific
  99.    // number of data.
  100.    //
  101.    // So, common chunk structure is as follows:
  102.    //
  103.    // 2 bytes                     Chunk code
  104.    // 1 byte                      Datatype code
  105.    // N bytes (datatype-specific) Chunk data
  106.    //
  107.    // 3.1 Chunk datatypes
  108.    // -------------------
  109.    // Code Name        Data length        Description
  110.    //                    (bytes)
  111.    // ---- --------  -----------------  --------------------------------------------------------------------------------
  112.    //    0 Empty            0           A chunk with no data
  113.    //    1 Byte             1           unsigned 1-byte (0..255)
  114.    //    2 Word             2           unsigned 2-byte (0..65535)
  115.    //    3 Int              4           signed 4-byte (-2147483648..2147483647)
  116.    //    4 StringB      1+(0..255)      Ansi string, with length Byte preceding (max length 255 bytes)
  117.    //    5 StringW     2+(0..65535)     Ansi string (not a *wide* string), with length Word preceding (max length 65 KB)
  118.    //    6 StringI   4+(0..2147483647)  Ansi string, with length Int preceding (max length 2 GB)
  119.    //
  120.    //  * Note on chunk data types:
  121.    //    Only Intel byte-order is used, where $12345678 is represented as byte
  122.    //    sequence: $78,$56,$34,$12.
  123.    //    Each chunk has its own, fixed datatype, which can never be changed in
  124.    //    later releases. Any mismatch with predefined chunk datatype must be
  125.    //    treated as error.
  126.    //
  127.    // 3.2 Chunk codes
  128.    // ---------------
  129.    // According to stated above, chunk code is an unsigned 2-byte number. The
  130.    // datatype and meaning of each chunk is described along with its
  131.    // declaration.
  132.    //
  133.    // Chunk codes $4000..$4fff are open-chunks for nested elements, always
  134.    // having Empty datatype. Each open-chunk has corresponding close-chunk
  135.    // with bit 15 set (ranged $c000..$cfff), eg:
  136.    //   $4000 (open) -> $c000 (close)
  137.    //   $4001 (open) -> $c001 (close)
  138.    // & so on. This allows correct recognition of close-chunks knowing none of
  139.    // the open-chunk purpose.
  140.    //
  141.    // 4. Common phoa-file Reader behaviour
  142.    // ====================================
  143.    // An algorithm designed for reading phoa-files (the Reader) must conform
  144.    // to the following rules:
  145.    //
  146.    // - The Reader reads file header:
  147.    //   o Signature must be exact copy of the PhoA file signature, any mismatch
  148.    //     leads to an error.
  149.    //   o Revision must be 3 or higher for the Reader to treat file as
  150.    //     chunk-based, otherwise this is not chunk-organized phoa-file, and
  151.    //     Reader must implement specific routines to handle it.
  152.    //   o Each specific Revision means possible INCOMPATIBILITIES compared to
  153.    //     lower Revision. So Reader must NOT read Revision it is not intended
  154.    //     to read. Though common file structure would be possibly leaved
  155.    //     intact, there might be some changes in chunk handling so the Reader
  156.    //     can only try to handle such file 'at its own risk'. Generally, this
  157.    //     format was designed just not to be updated frequently. The ability
  158.    //     to handle files with higher revisions is controlled by
  159.    //     StrictRevision conditional define, turn it off to let Reader to
  160.    //     continue with parsing file of 'wrong' revision.
  161.    // - In case Revision is 3 or higher (otherwise see note at the beginning of
  162.    //   this file):
  163.    //   o The Reader processes each chunk one-by-one. The main chunk principle
  164.    //     (and goal) is expansibility, so if the Reader encounters chunk not
  165.    //     known to it, it must just SKIP this chunk whole (along with its
  166.    //     data and/or nested chunks). The purpose of skipping chunk with its
  167.    //     nested chunks is that a chunk cannot be reliably recognized without
  168.    //     its context.
  169.    //     * For known chunk Reader then retrieves its stored datatype code and
  170.    //       does or does not verify this datatype. Generally, mismathing chunk
  171.    //       datatype may rise from broken phoa-files and must be treated as
  172.    //       error.
  173.    //     * If chunk is open-chunk (always with no data, see above), the Reader
  174.    //       handles all subsequent chunks till the terminating close-chunk as
  175.    //       nested (action depends on a particular case).
  176.    //     * If chunk is a data-chunk, it may or may not be processed, depending
  177.    //       on the Reader implementation.
  178.    //
  179.    // That is all for now. Yeah, quite long preface :)
  180.  
  181. {$DEFINE StrictRevision} // If defined, fail opening files with revisions higher than specified in this unit
  182.  
  183. type
  184.    // Possible chunk datatypes
  185.   TPhChunkDatatype = (pcdEmpty, pcdByte, pcdWord, pcdInt, pcdStringB, pcdStringW, pcdStringI);
  186.  
  187.   TPhChunkCode = Word;
  188.  
  189. const
  190.    // Phoa-file signature
  191.   SPhoAFileSignature               = 'PhoA [PhotoAlbum] project file'; // NEVER LOCALIZE!
  192.  
  193.    // Phoa-files revisions. Revision at index 0 is always the latest one
  194.   aPhFileRevisions: Array[0..2] of record
  195.     iNumber:  Integer; // Phoa-file Revision Number
  196.     sName:    String;  // Name of version family to recognize that revision
  197.     sMinName: String;  // Name of PhoA version introduced that revision
  198.   end = (
  199.     (iNumber: $0003; sName: 'PhoA 1.1+';  sMinName: '1.1.1a'),
  200.     (iNumber: $0002; sName: 'PhoA 1.0.x'; sMinName: '1.0.1a'),
  201.     (iNumber: $0001; sName: 'PhoA 0.x';   sMinName: '0.02b'));
  202.  
  203.    // Current photo album file Revision Number
  204.   IPhFileRevisionNumber            = $0003;
  205.    // Starting revision for chunk-based handling
  206.   IPhFile_MinChunkRevNumber        = $0003;
  207.  
  208.    //-------------------------------------------------------------------------------------------------------------------
  209.    // Chunk codes
  210.    //-------------------------------------------------------------------------------------------------------------------
  211.    // Base codes
  212.   IPhChunk_Open_Low                = $4000; // Low bound for all open-chunks
  213.   IPhChunk_Open_High               = $4fff; // High bound for all open-chunks
  214.   IPhChunk_Close_Low               = $c000; // Low bound for all close-chunks
  215.   IPhChunk_Close_High              = $cfff; // High bound for all close-chunks
  216.  
  217.    // General stuff
  218.   IPhChunk_Remark                  = $0000; // StringW  Any text, ignored by the Reader
  219.    // Common photo album data
  220.   IPhChunk_PhoaGenerator           = $1001; // StringB  Application created the album
  221.   IPhChunk_PhoaSavedDate           = $1002; // Int      Date file saved: number of days since Jan 01, 0001
  222.   IPhChunk_PhoaSavedTime           = $1003; // Int      Time file saved: number of seconds since midnight
  223.   IPhChunk_PhoaDescription         = $1010; // StringW  Photo album description text
  224.   IPhChunk_PhoaThumbQuality        = $1020; // Byte     Photo album thumbnail JPEG-quality level [1..100]
  225.   IPhChunk_PhoaThumbWidth          = $1021; // Word     Photo album thumbnail width  [32..1024]
  226.   IPhChunk_PhoaThumbHeight         = $1022; // Word     Photo album thumbnail height [32..1024]
  227.    // Picture properties
  228.   IPhChunk_Pic_ID                  = $1101; // Int      ID: an unique picture identifier, >=1
  229.   IPhChunk_Pic_ThumbnailData       = $1110; // StringI  Picture thumnail: JPEG data stream
  230.   IPhChunk_Pic_ThumbWidth          = $1111; // Word     Thumbnail width in pixels
  231.   IPhChunk_Pic_ThumbHeight         = $1112; // Word     Thumbnail height in pixels
  232.   IPhChunk_Pic_PicFileName         = $1120; // StringW  Absolute or relative (to the phoa-file) picture filename
  233.   IPhChunk_Pic_PicFileSize         = $1121; // Int      Picture file size, bytes
  234.   IPhChunk_Pic_PicWidth            = $1122; // Int      Image width, pixels
  235.   IPhChunk_Pic_PicHeight           = $1123; // Int      Image height, pixels
  236.   IPhChunk_Pic_PicFormat           = $1124; // Byte     Pixel image format ID (see below)
  237.   IPhChunk_Pic_Date                = $1130; // Int      Date: number of days since Jan 01, 0001
  238.   IPhChunk_Pic_Time                = $1131; // Int      Time: number of seconds since midnight
  239.   IPhChunk_Pic_Place               = $1132; // StringW  Place
  240.   IPhChunk_Pic_FilmNumber          = $1133; // StringW  Film number or name
  241.   IPhChunk_Pic_FrameNumber         = $1134; // StringB  Frame number
  242.   IPhChunk_Pic_Author              = $1135; // StringW  Picture author
  243.   IPhChunk_Pic_Media               = $1136; // StringW  Media name or code
  244.   IPhChunk_Pic_Desc                = $1137; // StringW  Description
  245.   IPhChunk_Pic_Notes               = $1138; // StringW  Notes
  246.   IPhChunk_Pic_Keywords            = $1139; // StringW  Keywords: Comma-separated list. Single entries containing spaces or commas must be double-quoted
  247.    // Picture group properties
  248.   IPhChunk_Group_Text              = $1201; // StringW  Group text (name)
  249.   IPhChunk_Group_Expanded          = $1202; // Byte     Group-node expanded flag (0/1)
  250.    // Picture linked in group properties
  251.   IPhChunk_GroupPic_ID             = $1220; // Int      Link to picture (the picture's ID)
  252.    // Photo album view properties
  253.   IPhChunk_View_Name               = $1301; // StringW  View name
  254.   IPhChunk_ViewGrouping_Prop       = $1310; // Word     View grouping property (see below)
  255.   IPhChunk_ViewGrouping_Unclass    = $1311; // Byte     View grouping switch: place unclassified pictures to a separate folder (0/1)
  256.   IPhChunk_ViewSorting_Prop        = $1320; // Word     View sorting property (see below)
  257.   IPhChunk_ViewSorting_Order       = $1321; // Byte     View sorting sort direction: 0=Ascending, 1=Descending
  258.    // Open-chunks
  259.   IPhChunk_Pics_Open               = $4010; // Empty    Open-chunk for photo album picture list
  260.   IPhChunk_Pic_Open                = $4011; // Empty    Open-chunk for single photo album picture entry (inside the list)
  261.   IPhChunk_Group_Open              = $4020; // Empty    Open-chunk for single picture group (root or nested)
  262.   IPhChunk_Groups_Open             = $4021; // Empty    Open-chunk for nested picture groups
  263.   IPhChunk_GroupPics_Open          = $4030; // Empty    Open-chunk for pics contained in the group
  264.   IPhChunk_Views_Open              = $4050; // Empty    Open-chunk for photo album views
  265.   IPhChunk_View_Open               = $4060; // Empty    Open-chunk for single photo album view
  266.   IPhChunk_ViewGroupings_Open      = $4061; // Empty    Open-chunk for photo album view groupings
  267.   IPhChunk_ViewGrouping_Open       = $4062; // Empty    Open-chunk for single photo album view grouping
  268.   IPhChunk_ViewSortings_Open       = $4063; // Empty    Open-chunk for photo album view sortings
  269.   IPhChunk_ViewSorting_Open        = $4064; // Empty    Open-chunk for single photo album view sorting
  270.    // Close-chunks
  271.   IPhChunk_Pics_Close              = $c010; // Empty    Close-chunk for photo album picture list
  272.   IPhChunk_Pic_Close               = $c011; // Empty    Close-chunk for single photo album picture entry (inside the list)
  273.   IPhChunk_Group_Close             = $c020; // Empty    Close-chunk for single picture group (root or nested)
  274.   IPhChunk_Groups_Close            = $c021; // Empty    Close-chunk for nested picture groups
  275.   IPhChunk_GroupPics_Close         = $c030; // Empty    Close-chunk for pics contained in the group
  276.   IPhChunk_Views_Close             = $c050; // Empty    Close-chunk for photo album views
  277.   IPhChunk_View_Close              = $c060; // Empty    Close-chunk for single photo album view
  278.   IPhChunk_ViewGroupings_Close     = $c061; // Empty    Close-chunk for photo album view groupings
  279.   IPhChunk_ViewGrouping_Close      = $c062; // Empty    Close-chunk for single photo album view grouping
  280.   IPhChunk_ViewSortings_Close      = $c063; // Empty    Close-chunk for photo album view sortings
  281.   IPhChunk_ViewSorting_Close       = $c064; // Empty    Close-chunk for single photo album view sorting
  282.  
  283. type
  284.    // Local chunk entry, used in aPhChunks[]
  285.   PPhChunkEntry = ^TPhChunkEntry;
  286.   TPhChunkEntry = record
  287.     wCode:     TPhChunkCode;     // Chunk code, one of the IPhChunk_xxxxxx constants
  288.     Datatype:  TPhChunkDatatype; // Predefined chunk datatype
  289.     iRangeMin: Integer;          // Low value limit (for ordinal values)
  290.     iRangeMax: Integer;          // High value limit (for ordinal values)
  291.   end;
  292.  
  293.    // List of chunks known for the moment, and their datatypes and ranges
  294. const
  295.   aPhChunks: Array[0..56] of TPhChunkEntry = (
  296.     (wCode: IPhChunk_Remark;                 Datatype: pcdStringW),
  297.     (wCode: IPhChunk_PhoaGenerator;          Datatype: pcdStringB),
  298.     (wCode: IPhChunk_PhoaSavedDate;          Datatype: pcdInt;    iRangeMin: 0;  iRangeMax: 3652058 {Dec 31, 9999}),
  299.     (wCode: IPhChunk_PhoaSavedTime;          Datatype: pcdInt;    iRangeMin: 0;  iRangeMax: 24*60*60),
  300.     (wCode: IPhChunk_PhoaDescription;        Datatype: pcdStringW),
  301.     (wCode: IPhChunk_PhoaThumbQuality;       Datatype: pcdByte;   iRangeMin: 1;  iRangeMax: 100),
  302.     (wCode: IPhChunk_PhoaThumbWidth;         Datatype: pcdWord;   iRangeMin: 32; iRangeMax: 1024),
  303.     (wCode: IPhChunk_PhoaThumbHeight;        Datatype: pcdWord;   iRangeMin: 32; iRangeMax: 1024),
  304.     (wCode: IPhChunk_Pic_ID;                 Datatype: pcdInt;    iRangeMin: 1;  iRangeMax: High(Integer)),
  305.     (wCode: IPhChunk_Pic_ThumbnailData;      Datatype: pcdStringI),
  306.     (wCode: IPhChunk_Pic_ThumbWidth;         Datatype: pcdWord;   iRangeMin: 32; iRangeMax: 1024),
  307.     (wCode: IPhChunk_Pic_ThumbHeight;        Datatype: pcdWord;   iRangeMin: 32; iRangeMax: 1024),
  308.     (wCode: IPhChunk_Pic_PicFileName;        Datatype: pcdStringW),
  309.     (wCode: IPhChunk_Pic_PicFileSize;        Datatype: pcdInt;    iRangeMin: 0;  iRangeMax: High(Integer)),
  310.     (wCode: IPhChunk_Pic_PicWidth;           Datatype: pcdInt;    iRangeMin: 0;  iRangeMax: High(Integer)),
  311.     (wCode: IPhChunk_Pic_PicHeight;          Datatype: pcdInt;    iRangeMin: 0;  iRangeMax: High(Integer)),
  312.     (wCode: IPhChunk_Pic_PicFormat;          Datatype: pcdByte;   iRangeMin: 0;  iRangeMax: 8),
  313.     (wCode: IPhChunk_Pic_Date;               Datatype: pcdInt;    iRangeMin: 0;  iRangeMax: 3652058 {Dec 31, 9999}),
  314.     (wCode: IPhChunk_Pic_Time;               Datatype: pcdInt;    iRangeMin: 0;  iRangeMax: 24*60*60),
  315.     (wCode: IPhChunk_Pic_Place;              Datatype: pcdStringW),
  316.     (wCode: IPhChunk_Pic_FilmNumber;         Datatype: pcdStringW),
  317.     (wCode: IPhChunk_Pic_FrameNumber;        Datatype: pcdStringB),
  318.     (wCode: IPhChunk_Pic_Author;             Datatype: pcdStringW),
  319.     (wCode: IPhChunk_Pic_Media;              Datatype: pcdStringW),
  320.     (wCode: IPhChunk_Pic_Desc;               Datatype: pcdStringW),
  321.     (wCode: IPhChunk_Pic_Notes;              Datatype: pcdStringW),
  322.     (wCode: IPhChunk_Pic_Keywords;           Datatype: pcdStringW),
  323.     (wCode: IPhChunk_Group_Text;             Datatype: pcdStringW),
  324.     (wCode: IPhChunk_Group_Expanded;         Datatype: pcdByte;   iRangeMin: 0;  iRangeMax: 1),
  325.     (wCode: IPhChunk_GroupPic_ID;            Datatype: pcdInt;    iRangeMin: 1;  iRangeMax: High(Integer)),
  326.     (wCode: IPhChunk_View_Name;              Datatype: pcdStringW),
  327.     (wCode: IPhChunk_ViewGrouping_Prop;      Datatype: pcdWord;   iRangeMin: 0;  iRangeMax: 10),
  328.     (wCode: IPhChunk_ViewGrouping_Unclass;   Datatype: pcdByte;   iRangeMin: 0;  iRangeMax: 1),
  329.     (wCode: IPhChunk_ViewSorting_Prop;       Datatype: pcdWord;   iRangeMin: 0;  iRangeMax: 19),
  330.     (wCode: IPhChunk_ViewSorting_Order;      Datatype: pcdByte;   iRangeMin: 0;  iRangeMax: 1),
  331.     (wCode: IPhChunk_Pics_Open;              Datatype: pcdEmpty),
  332.     (wCode: IPhChunk_Pic_Open;               Datatype: pcdEmpty),
  333.     (wCode: IPhChunk_Group_Open;             Datatype: pcdEmpty),
  334.     (wCode: IPhChunk_Groups_Open;            Datatype: pcdEmpty),
  335.     (wCode: IPhChunk_GroupPics_Open;         Datatype: pcdEmpty),
  336.     (wCode: IPhChunk_Views_Open;             Datatype: pcdEmpty),
  337.     (wCode: IPhChunk_View_Open;              Datatype: pcdEmpty),
  338.     (wCode: IPhChunk_ViewGroupings_Open;     Datatype: pcdEmpty),
  339.     (wCode: IPhChunk_ViewGrouping_Open;      Datatype: pcdEmpty),
  340.     (wCode: IPhChunk_ViewSortings_Open;      Datatype: pcdEmpty),
  341.     (wCode: IPhChunk_ViewSorting_Open;       Datatype: pcdEmpty),
  342.     (wCode: IPhChunk_Pics_Close;             Datatype: pcdEmpty),
  343.     (wCode: IPhChunk_Pic_Close;              Datatype: pcdEmpty),
  344.     (wCode: IPhChunk_Group_Close;            Datatype: pcdEmpty),
  345.     (wCode: IPhChunk_Groups_Close;           Datatype: pcdEmpty),
  346.     (wCode: IPhChunk_GroupPics_Close;        Datatype: pcdEmpty),
  347.     (wCode: IPhChunk_Views_Close;            Datatype: pcdEmpty),
  348.     (wCode: IPhChunk_View_Close;             Datatype: pcdEmpty),
  349.     (wCode: IPhChunk_ViewGroupings_Close;    Datatype: pcdEmpty),
  350.     (wCode: IPhChunk_ViewGrouping_Close;     Datatype: pcdEmpty),
  351.     (wCode: IPhChunk_ViewSortings_Close;     Datatype: pcdEmpty),
  352.     (wCode: IPhChunk_ViewSorting_Close;      Datatype: pcdEmpty));
  353.  
  354.    // Possible values for Pixel Format are (see note below):
  355.    //
  356.    // Value  Default  Description
  357.    // -----  -------  --------------
  358.    //   0             Device-dependent
  359.    //   1             1-bit
  360.    //   2             4-bit
  361.    //   3             8-bit
  362.    //   4             15-bit
  363.    //   5             16-bit
  364.    //   6             24-bit
  365.    //   7             32-bit
  366.    //   8       *     Custom or unknown
  367.    //
  368.    // Possible values for Grouping Property are (see note below):
  369.    //
  370.    // Value  Description
  371.    // -----  --------------
  372.    //   0    Picture file path
  373.    //   1    Date year
  374.    //   2    Date month
  375.    //   3    Date day
  376.    //   4    Time hour
  377.    //   5    Time minute
  378.    //   6    Place
  379.    //   7    Film number
  380.    //   8    Author
  381.    //   9    Media name/code
  382.    //  10    Keywords
  383.    //
  384.    // Possible values for Sorting Property are (see note below):
  385.    //
  386.    // Value  Description
  387.    // -----  --------------
  388.    //   0    ID
  389.    //   1    Picture filename
  390.    //   2    Picture filename with path
  391.    //   3    Picture file path
  392.    //   4    Picture file size
  393.    //   5    Picture file size in bytes (for sorting is just the same as 'Picture file size')
  394.    //   6    Image width
  395.    //   7    Image height
  396.    //   8    Image dimensions
  397.    //   9    Pixel format
  398.    //  10    Date
  399.    //  11    Time
  400.    //  12    Place
  401.    //  13    Film number
  402.    //  14    Frame number
  403.    //  15    Author
  404.    //  16    Description
  405.    //  17    Notes
  406.    //  18    Media
  407.    //  19    Keywords (keywords are always ordered alphabetically, case-insensitively)
  408.    //
  409.    // --------
  410.    // * Note on 'enumerated' values: the Reader should IGNORE values with
  411.    //     unknown code, this allows to extend specifications in future. The
  412.    //     'Default' value is just the one used for initializing (may be helpful
  413.    //     in such case), if applicable.
  414.  
  415.    // TPhoaStreamer status constants
  416.   IPhStatus_OK                     =  0; // Succeeded
  417.   IPhStatus_InvalidMode            =  1; // Invalid opening mode (trying to write in read mode and vice versa)
  418.   IPhStatus_CannotRead             =  2; // Stream read error
  419.   IPhStatus_CannotWrite            =  3; // Stream write error
  420.   IPhStatus_CannotAlterRevision    =  4; // Trying to modify Revision Number after some data have been written
  421.   IPhStatus_InvalidSignature       =  5; // Invalid phoa-file signature
  422.   IPhStatus_NotAChunkedFile        =  6; // File being open is not a chunk-based one, cannot be handled with this unit
  423.   IPhStatus_FileRevNewer           =  7; // File being open has higher revision than possible with this unit (was created with the newer program version)
  424.   IPhStatus_UnknownChunkToWrite    =  8; // Code of a chunk is unknown
  425.   IPhStatus_WrongDatatypePassed    =  9; // Wrong datatype passed to a WriteChunkxxxx() procedure
  426.   IPhStatus_InvalidDatatype        = 10; // Chunk datatype invalid or unknown
  427.  
  428. type
  429.    //-------------------------------------------------------------------------------------------------------------------
  430.    // Basic implementation of I/O routines (btw used in PhoA)
  431.    //-------------------------------------------------------------------------------------------------------------------
  432.  
  433.    // Base Exception class
  434.   EPhoaStreamerError = class(Exception)
  435.   private
  436.     FErrorCode: Integer;
  437.   public
  438.     constructor Create(const Msg: String; iErrCode: Integer);
  439.     constructor CreateFmt(const Msg: String; const Args: Array of const; iErrCode: Integer);
  440.      // Props
  441.      // -- Code of error encountered, one of the IPhStatus_xxxxxx constants
  442.     property ErrorCode: Integer read FErrorCode;
  443.   end;
  444.  
  445.   TPhoaStreamingMode = (psmRead, psmWrite);
  446.  
  447.    // Possible ReadChunk result
  448.   TPhReadingChunkResult = (
  449.     rcrOK,               // No errors
  450.     rcrUnknown,          // Chunk code is unknown
  451.     rcrInvalidDatatype,  // Datatype invalid or unknown (out of range)
  452.     rcrDatatypeMismatch, // Datatype mismatch
  453.     rcrEOF);             // No more chunks (end of file encountered)
  454.  
  455.    // Base class for storing/reading photo album data to/from a stream
  456.   TPhoaStreamer = class(TObject)
  457.   private
  458.      // Prop storage
  459.     FStream: TStream;
  460.     FMode: TPhoaStreamingMode;
  461.     FTransferredBytes: Cardinal;
  462.     FRevisionNumber: Integer;
  463.     FBasePath: String;
  464.      // Prop handlers
  465.     procedure SetRevisionNumber(Value: Integer);
  466.     function  GetChunked: Boolean;
  467.   protected
  468.      // Writes specified number of bytes from Buffer to the file
  469.     procedure Write(const Buffer; iSize: Integer);
  470.      // Reads specified number of bytes from the file into Buffer
  471.     procedure Read(var Buffer; iSize: Integer);
  472.      // Raises an exception if RequiredMode<>Mode
  473.     procedure CheckMode(RequiredMode: TPhoaStreamingMode);
  474.      // Checking header data validity, virtual to have possibility to alter behaviour in a descendant
  475.     procedure ValidateSignature(const sReadSignature: String); virtual;
  476.     procedure ValidateRevision; virtual;
  477.   public
  478.     constructor Create(AStream: TStream; AMode: TPhoaStreamingMode; const sBasePath: String);
  479.      // Writing/reading routines for typed data
  480.     procedure WriteByte(b: Byte);
  481.     procedure WriteWord(w: Word);
  482.     procedure WriteInt(i: Integer);
  483.     procedure WriteStringB(const s: String);
  484.     procedure WriteStringW(const s: String);
  485.     procedure WriteStringI(const s: String);
  486.     function  ReadByte: Byte;
  487.     function  ReadWord: Word;
  488.     function  ReadInt: Integer;
  489.     function  ReadStringB: String;
  490.     function  ReadStringW: String;
  491.     function  ReadStringI: String;
  492.      // Writes/reads file header
  493.     procedure WriteHeader;
  494.     procedure ReadHeader;
  495.      // Writing/reading chunks (NO DATA are being written/read, only chunk code and datatype!)
  496.      // -- Version with auto-detecting chunk datatype
  497.     procedure WriteChunk(Code: TPhChunkCode); overload;
  498.      // -- Version with forced chunk datatype
  499.     procedure WriteChunk(Code: TPhChunkCode; Datatype: TPhChunkDatatype); overload;
  500.      // -- Version writing typed values (strict datatype)
  501.     procedure WriteChunkByte(Code: TPhChunkCode; b: Byte);
  502.     procedure WriteChunkWord(Code: TPhChunkCode; w: Word);
  503.     procedure WriteChunkInt(Code: TPhChunkCode; i: Integer);
  504.     procedure WriteChunkString(Code: TPhChunkCode; const s: String); // Auto-detecting type
  505.      // -- Reads chunk code and datatype
  506.     function  ReadChunk(out Code: TPhChunkCode; out Datatype: TPhChunkDatatype): TPhReadingChunkResult;
  507.      // -- Reads chunk code, datatype and value. Value is being read only if result is not rcrInvalidDatatype or rcrEOF,
  508.      //    invalid datatype raises exception. bSkipUnknown controls whether to skip unknown chunks (including all nested,
  509.      //    if necessary). bSkipUnmatched controls whether to skip chunks which datatype mismatches from predefined one
  510.     function  ReadChunkValue(out Code: TPhChunkCode; out Datatype: TPhChunkDatatype; var vValue: Variant; bSkipUnknown, bSkipUnmatched: Boolean): TPhReadingChunkResult;
  511.      // Skips all chunks until close-chunk for OpenCode chunk code encountered. May be used for ignoring unknown
  512.      //   open-chunks. It is valid to call SkipNestedChunks for non-open-chunks (ignored if it's the case)
  513.     procedure SkipNestedChunks(OpenCode: TPhChunkCode);
  514.      // Props
  515.      // -- True if chunk-based revision used
  516.     property Chunked: Boolean read GetChunked;
  517.      // -- Photo album file path (for translating relative picture file paths)
  518.     property BasePath: String read FBasePath;
  519.      // -- Mode in which the object was created
  520.     property Mode: TPhoaStreamingMode read FMode;
  521.      // -- PhoA Revision Number, readonly in Read mode; can only be modified before any data are written
  522.     property RevisionNumber: Integer read FRevisionNumber write SetRevisionNumber;
  523.      // -- Stream used for transferring data
  524.     property Stream: TStream read FStream;
  525.      // -- Number of bytes read or written from/to the stream
  526.     property TransferredBytes: Cardinal read FTransferredBytes;
  527.   end;
  528.  
  529.    // Class for storing/reading photo album data to/from a file
  530.   TPhoaFiler = class(TPhoaStreamer)
  531.   private
  532.     FFilename: String;
  533.   public
  534.     constructor Create(AMode: TPhoaStreamingMode; const sFilename: String);
  535.     destructor Destroy; override;
  536.      // Props
  537.      // -- Open file name
  538.     property Filename: String read FFilename;
  539.   end;
  540.  
  541.    //-------------------------------------------------------------------------------------------------------------------
  542.    // Utility routines
  543.    //-------------------------------------------------------------------------------------------------------------------
  544.  
  545.    // Searches aPhChunks[] for a chunk code, returns pointer to entry if found, otherwise nil.
  546.   function FindChunk(Code: TPhChunkCode): PPhChunkEntry;
  547.    // The same as FindChunk(), but never returns nil; if Code not found, raises exception
  548.   function FindChunkStrict(Code: TPhChunkCode): PPhChunkEntry;
  549.    // Checking whether chunk Code is an open-chunk
  550.   function IsOpenChunk(Code: TPhChunkCode): Boolean;
  551.    // Checking whether chunk Code is close-chunk for chunk OpenCode
  552.   function IsCloseChunk(Code, OpenCode: TPhChunkCode): Boolean;
  553.  
  554.    // Date and time conversion
  555.    // Converts date into number of days passed since Jan 01, 0001
  556.   function  DateToPhoaDate(const Date: TDateTime): Integer;
  557.    // Converts time into number of seconds passed since midnight
  558.   function  TimeToPhoaTime(const Time: TDateTime): Integer;
  559.    // Converts phoa-date (days since Jan 01, 0001) into TDateTime type
  560.   function  PhoaDateToDate(iDate: Integer): TDateTime;
  561.    // Converts phoa-time (number of seconds since midnight) into TDateTime type
  562.   function  PhoaTimeToTime(iTime: Integer): TDateTime;
  563.  
  564. resourcestring
  565.    // Error messages
  566.   SPhStreamErr_InvalidMode         = 'Invalid opening mode';
  567.   SPhStreamErr_CannotRead          = 'Cannot read %d bytes from the stream';
  568.   SPhStreamErr_CannotWrite         = 'Cannot write %d bytes to the stream';
  569.   SPhStreamErr_CannotAlterRevision = 'RevisionNumber cannot be modified after some data have been written';
  570.   SPhStreamErr_InvalidSinature     = 'Invalid file signature';
  571.   SPhStreamErr_NotAChunkedFile     = 'File is not chunk-based (old format)';
  572.   SPhStreamErr_FileRevNewer        = 'File was created by the program version newer than this. The file cannot be loaded';
  573.   SPhStreamErr_UnknownChunkToWrite = 'Unknown chunk code to write (%d)';
  574.   SPhStreamErr_WrongDatatypePassed = 'Wrong Datatype of chunk passed to WriteChunkxxxxxx() (%s needed)';
  575.   SPhStreamErr_InvalidDatatype     = 'Chunk datatype invalid or unknown (code: %d)';
  576.  
  577. implementation
  578. uses Math, Variants;
  579.  
  580.   function FindChunk(Code: TPhChunkCode): PPhChunkEntry;
  581.   var i: Integer;
  582.   begin
  583.     for i := 0 to High(aPhChunks) do begin
  584.       Result := @aPhChunks[i];
  585.       if Result^.wCode=Code then Exit;
  586.     end;
  587.     Result := nil;
  588.   end;
  589.  
  590.   function FindChunkStrict(Code: TPhChunkCode): PPhChunkEntry;
  591.   begin
  592.     Result := FindChunk(Code);
  593.     if Result=nil then
  594.       raise EPhoaStreamerError.CreateFmt(SPhStreamErr_UnknownChunkToWrite, [Code], IPhStatus_UnknownChunkToWrite);
  595.   end;
  596.  
  597.   function IsOpenChunk(Code: TPhChunkCode): Boolean;
  598.   begin
  599.     Result := (Code>=IPhChunk_Open_Low) and (Code<=IPhChunk_Open_High);
  600.   end;
  601.  
  602.   function IsCloseChunk(Code, OpenCode: TPhChunkCode): Boolean;
  603.   begin
  604.     Result := (Code>=IPhChunk_Close_Low) and (Code<=IPhChunk_Close_High) and (Code=OpenCode or $8000);
  605.   end;
  606.  
  607.   function DateToPhoaDate(const Date: TDateTime): Integer;
  608.   begin
  609.     Result := Trunc(Date)-Trunc(EncodeDate(0001, 01, 01));
  610.   end;
  611.  
  612.   function TimeToPhoaTime(const Time: TDateTime): Integer;
  613.   begin
  614.     Result := Trunc(24*60*60*Frac(Time));
  615.   end;
  616.  
  617.   function PhoaDateToDate(iDate: Integer): TDateTime;
  618.   begin
  619.     Result := iDate+EncodeDate(0001, 01, 01);
  620.   end;
  621.  
  622.   function PhoaTimeToTime(iTime: Integer): TDateTime;
  623.   begin
  624.     Result := Frac(iTime/(24*60*60));
  625.   end;
  626.  
  627.    // Raises 'Wrong Datatype passed' exception
  628.   procedure WrongWriteChunkDatatype(const sRequiredName: String);
  629.   begin
  630.     raise EPhoaStreamerError.CreateFmt(SPhStreamErr_WrongDatatypePassed, [sRequiredName], IPhStatus_WrongDatatypePassed);
  631.   end;
  632.  
  633.    //-------------------------------------------------------------------------------------------------------------------
  634.    // EPhoaStreamerError
  635.    //-------------------------------------------------------------------------------------------------------------------
  636.  
  637.   constructor EPhoaStreamerError.Create(const Msg: String; iErrCode: Integer);
  638.   begin
  639.     inherited Create(Msg);
  640.     FErrorCode := iErrCode;
  641.   end;
  642.  
  643.   constructor EPhoaStreamerError.CreateFmt(const Msg: String; const Args: Array of const; iErrCode: Integer);
  644.   begin
  645.     inherited CreateFmt(Msg, Args);
  646.     FErrorCode := iErrCode;
  647.   end;
  648.  
  649.    //-------------------------------------------------------------------------------------------------------------------
  650.    // TPhoaStreamer
  651.    //-------------------------------------------------------------------------------------------------------------------
  652.  
  653.   procedure TPhoaStreamer.CheckMode(RequiredMode: TPhoaStreamingMode);
  654.   begin
  655.     if FMode<>RequiredMode then raise EPhoaStreamerError.Create(SPhStreamErr_InvalidMode, IPhStatus_InvalidMode);
  656.   end;
  657.  
  658.   constructor TPhoaStreamer.Create(AStream: TStream; AMode: TPhoaStreamingMode; const sBasePath: String);
  659.   begin
  660.     inherited Create;
  661.     FStream         := AStream;
  662.     FMode           := AMode;
  663.     FBasePath       := sBasePath;
  664.     FRevisionNumber := IPhFileRevisionNumber; // Assume most modern revision by default
  665.   end;
  666.  
  667.   function TPhoaStreamer.GetChunked: Boolean;
  668.   begin
  669.     Result := FRevisionNumber>=IPhFile_MinChunkRevNumber;
  670.   end;
  671.  
  672.   procedure TPhoaStreamer.Read(var Buffer; iSize: Integer);
  673.   begin
  674.     CheckMode(psmRead);
  675.     if FStream.Read(Buffer, iSize)<>iSize then
  676.       raise EPhoaStreamerError.CreateFmt(SPhStreamErr_CannotRead, [iSize], IPhStatus_CannotRead);
  677.     Inc(FTransferredBytes, iSize);
  678.   end;
  679.  
  680.   function TPhoaStreamer.ReadByte: Byte;
  681.   begin
  682.     Read(Result, SizeOf(Result));
  683.   end;
  684.  
  685.   function TPhoaStreamer.ReadChunk(out Code: TPhChunkCode; out Datatype: TPhChunkDatatype): TPhReadingChunkResult;
  686.   var pe: PPhChunkEntry;
  687.   begin
  688.      // Check if there are data at all
  689.     if FStream.Position>=FStream.Size then begin
  690.       Code     := 0;
  691.       Datatype := pcdEmpty;
  692.       Result   := rcrEOF;
  693.      // Read the chunk and its datatype
  694.     end else begin
  695.       Code     := ReadWord;
  696.       Datatype := TPhChunkDatatype(ReadByte);
  697.        // Try to find a chunk
  698.       pe := FindChunk(Code);
  699.        // Validate Datatype
  700.       if not (Datatype in [Low(Datatype)..High(Datatype)]) then Result := rcrInvalidDatatype
  701.        // If not found
  702.       else if pe=nil then Result := rcrUnknown
  703.        // Compare Datatype
  704.       else if Datatype<>pe.Datatype then Result := rcrDatatypeMismatch
  705.        // Ok
  706.       else Result := rcrOK;
  707.     end;
  708.   end;
  709.  
  710.   function TPhoaStreamer.ReadChunkValue(out Code: TPhChunkCode; out Datatype: TPhChunkDatatype; var vValue: Variant; bSkipUnknown, bSkipUnmatched: Boolean): TPhReadingChunkResult;
  711.   var pe: PPhChunkEntry;
  712.   begin
  713.     vValue := Null;
  714.     repeat
  715.       Result := ReadChunk(Code, Datatype);
  716.       case Result of
  717.         rcrInvalidDatatype:  raise EPhoaStreamerError.CreateFmt(SPhStreamErr_InvalidDatatype, [Byte(Datatype)], IPhStatus_InvalidDatatype);
  718.         rcrEOF:              Break;
  719.       end;
  720.        // Read the value
  721.       case Datatype of
  722.         pcdByte:    vValue := ReadByte;
  723.         pcdWord:    vValue := ReadWord;
  724.         pcdInt:     vValue := ReadInt;
  725.         pcdStringB: vValue := ReadStringB;
  726.         pcdStringW: vValue := ReadStringW;
  727.         pcdStringI: vValue := ReadStringI;
  728.       end;
  729.        // Validate ranges for ordinal types. Outranged values assume unmatched
  730.       if (Result=rcrOK) and (Datatype in [pcdByte, pcdWord, pcdInt]) then begin
  731.         pe := FindChunk(Code);
  732.         if (vValue<pe.iRangeMin) or (vValue>pe.iRangeMax) then Result := rcrDatatypeMismatch;
  733.       end;
  734.        // Check the chunk for validity
  735.       case Result of
  736.         rcrOK:               Break;
  737.         rcrUnknown:          if not bSkipUnknown then Break;
  738.         rcrDatatypeMismatch: if not bSkipUnmatched then Break;
  739.       end;
  740.        // Chunk is to be skipped, if we're here. Check if it's nested one
  741.       SkipNestedChunks(Code);
  742.     until False;
  743.   end;
  744.  
  745.   procedure TPhoaStreamer.ReadHeader;
  746.   var s: String;
  747.   begin
  748.      // Load and check signature
  749.     SetLength(s, Length(SPhoAFileSignature));
  750.     Read(s[1], Length(s));
  751.     ValidateSignature(s);
  752.      // Read Revision Number
  753.     FRevisionNumber := ReadInt;
  754.     ValidateRevision;
  755.   end;
  756.  
  757.   function TPhoaStreamer.ReadInt: Integer;
  758.   begin
  759.     Read(Result, SizeOf(Result));
  760.   end;
  761.  
  762.   function TPhoaStreamer.ReadStringB: String;
  763.   var b: Byte;
  764.   begin
  765.     b := ReadByte;
  766.     SetLength(Result, b);
  767.     Read(Result[1], b);
  768.   end;
  769.  
  770.   function TPhoaStreamer.ReadStringI: String;
  771.   var i: Integer;
  772.   begin
  773.     i := ReadInt;
  774.     SetLength(Result, i);
  775.     Read(Result[1], i);
  776.   end;
  777.  
  778.   function TPhoaStreamer.ReadStringW: String;
  779.   var w: Word;
  780.   begin
  781.     w := ReadWord;
  782.     SetLength(Result, w);
  783.     Read(Result[1], w);
  784.   end;
  785.  
  786.   function TPhoaStreamer.ReadWord: Word;
  787.   begin
  788.     Read(Result, SizeOf(Result));
  789.   end;
  790.  
  791.   procedure TPhoaStreamer.SetRevisionNumber(Value: Integer);
  792.   begin
  793.     CheckMode(psmWrite);
  794.     if FTransferredBytes>0 then
  795.       raise EPhoaStreamerError.Create(SPhStreamErr_CannotAlterRevision, IPhStatus_CannotAlterRevision);
  796.     FRevisionNumber := Value;
  797.   end;
  798.  
  799.   procedure TPhoaStreamer.SkipNestedChunks(OpenCode: TPhChunkCode);
  800.   var
  801.     wCode: TPhChunkCode;
  802.     Datatype: TPhChunkDatatype;
  803.     vValue: Variant;
  804.   begin
  805.     if IsOpenChunk(OpenCode) then
  806.       repeat
  807.         if ReadChunkValue(wCode, Datatype, vValue, True, True)=rcrEOF then Break;
  808.          // If nested-chunk-structure encountered, recurse it
  809.         SkipNestedChunks(wCode);
  810.       until IsCloseChunk(wCode, OpenCode);
  811.   end;
  812.  
  813.   procedure TPhoaStreamer.ValidateRevision;
  814.   begin
  815.      // Check that we are dealing with chunk-based file
  816.     if not Chunked then raise EPhoaStreamerError.Create(SPhStreamErr_NotAChunkedFile, IPhStatus_NotAChunkedFile);
  817.     {$IFDEF StrictRevision}
  818.      // Check that RevisionNumber is 'normal' for proper handling
  819.     if FRevisionNumber>IPhFileRevisionNumber then
  820.       raise EPhoaStreamerError.Create(SPhStreamErr_FileRevNewer, IPhStatus_FileRevNewer);
  821.     {$ENDIF StrictRevision}
  822.   end;
  823.  
  824.   procedure TPhoaStreamer.ValidateSignature(const sReadSignature: String);
  825.   begin
  826.     if sReadSignature<>SPhoAFileSignature then
  827.       raise EPhoaStreamerError.Create(SPhStreamErr_InvalidSinature, IPhStatus_InvalidSignature);
  828.   end;
  829.  
  830.   procedure TPhoaStreamer.Write(const Buffer; iSize: Integer);
  831.   begin
  832.     CheckMode(psmWrite);
  833.     if FStream.Write(Buffer, iSize)<>iSize then
  834.       raise EPhoaStreamerError.CreateFmt(SPhStreamErr_CannotWrite, [iSize], IPhStatus_CannotWrite);
  835.     Inc(FTransferredBytes, iSize);
  836.   end;
  837.  
  838.   procedure TPhoaStreamer.WriteByte(b: Byte);
  839.   begin
  840.     Write(b, SizeOf(b));
  841.   end;
  842.  
  843.   procedure TPhoaStreamer.WriteChunk(Code: TPhChunkCode);
  844.   begin
  845.     WriteChunk(Code, FindChunkStrict(Code)^.Datatype);
  846.   end;
  847.  
  848.   procedure TPhoaStreamer.WriteChunk(Code: TPhChunkCode; Datatype: TPhChunkDatatype);
  849.   begin
  850.     WriteWord(Code);
  851.     WriteByte(Byte(Datatype));
  852.   end;
  853.  
  854.   procedure TPhoaStreamer.WriteChunkByte(Code: TPhChunkCode; b: Byte);
  855.   var pe: PPhChunkEntry;
  856.   begin
  857.      // Find chunk entry
  858.     pe := FindChunkStrict(Code);
  859.     if pe.Datatype<>pcdByte then WrongWriteChunkDatatype('Byte');
  860.      // Write chunk/datatype code
  861.     WriteChunk(Code, pcdByte);
  862.      // Write chunk data
  863.     WriteByte(b);
  864.   end;
  865.  
  866.   procedure TPhoaStreamer.WriteChunkInt(Code: TPhChunkCode; i: Integer);
  867.   var pe: PPhChunkEntry;
  868.   begin
  869.      // Find chunk entry
  870.     pe := FindChunkStrict(Code);
  871.     if pe.Datatype<>pcdInt then WrongWriteChunkDatatype('Int');
  872.      // Write chunk/datatype code
  873.     WriteChunk(Code, pcdInt);
  874.      // Write chunk data
  875.     WriteInt(i);
  876.   end;
  877.  
  878.   procedure TPhoaStreamer.WriteChunkString(Code: TPhChunkCode; const s: String);
  879.   var pe: PPhChunkEntry;
  880.   begin
  881.      // Find chunk entry
  882.     pe := FindChunkStrict(Code);
  883.     if not (pe.Datatype in [pcdStringB, pcdStringW, pcdStringI]) then WrongWriteChunkDatatype('StringB, StringW or StringI');
  884.      // Write chunk/datatype code
  885.     WriteChunk(Code, pe.Datatype);
  886.      // Write chunk data
  887.     case pe.Datatype of
  888.       pcdStringB: WriteStringB(s);
  889.       pcdStringW: WriteStringW(s);
  890.       pcdStringI: WriteStringI(s);
  891.     end;
  892.   end;
  893.  
  894.   procedure TPhoaStreamer.WriteChunkWord(Code: TPhChunkCode; w: Word);
  895.   var pe: PPhChunkEntry;
  896.   begin
  897.      // Find chunk entry
  898.     pe := FindChunkStrict(Code);
  899.     if pe.Datatype<>pcdWord then WrongWriteChunkDatatype('Word');
  900.      // Write chunk/datatype code
  901.     WriteChunk(Code, pcdWord);
  902.      // Write chunk data
  903.     WriteWord(w);
  904.   end;
  905.  
  906.   procedure TPhoaStreamer.WriteHeader;
  907.   var s: String;
  908.   begin
  909.      // Write signature
  910.     s := SPhoAFileSignature;
  911.     Write(s[1], Length(s));
  912.      // Write Revision Number
  913.     WriteInt(FRevisionNumber);
  914.   end;
  915.  
  916.   procedure TPhoaStreamer.WriteInt(i: Integer);
  917.   begin
  918.     Write(i, SizeOf(i));
  919.   end;
  920.  
  921.   procedure TPhoaStreamer.WriteStringB(const s: String);
  922.   var b: Byte;
  923.   begin
  924.     b := Min(High(b), Length(s));
  925.     WriteByte(b);
  926.     if b>0 then Write(s[1], b);
  927.   end;
  928.  
  929.   procedure TPhoaStreamer.WriteStringI(const s: String);
  930.   var i: Integer;
  931.   begin
  932.     i := Length(s);
  933.     WriteInt(i);
  934.     if i>0 then Write(s[1], i);
  935.   end;
  936.  
  937.   procedure TPhoaStreamer.WriteStringW(const s: String);
  938.   var w: Word;
  939.   begin
  940.     w := Min(High(w), Length(s));
  941.     WriteWord(w);
  942.     if w>0 then Write(s[1], w);
  943.   end;
  944.  
  945.   procedure TPhoaStreamer.WriteWord(w: Word);
  946.   begin
  947.     Write(w, SizeOf(w));
  948.   end;
  949.  
  950.    //-------------------------------------------------------------------------------------------------------------------
  951.    // TPhoaFiler
  952.    //-------------------------------------------------------------------------------------------------------------------
  953.  
  954.   constructor TPhoaFiler.Create(AMode: TPhoaStreamingMode; const sFilename: String);
  955.   const aFileMode: Array[TPhoaStreamingMode] of Word = (fmOpenRead or fmShareDenyWrite, fmCreate);
  956.   begin
  957.     inherited Create(TFileStream.Create(sFilename, aFileMode[AMode]), AMode, ExtractFilePath(sFilename));
  958.     FFilename := sFilename;
  959.   end;
  960.  
  961.   destructor TPhoaFiler.Destroy;
  962.   begin
  963.     Stream.Free;
  964.     inherited Destroy;
  965.   end;
  966.  
  967. end.
  968.